package cn.wolfcode.service.impl;

import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.Result;
import cn.wolfcode.domain.*;
import cn.wolfcode.feign.IntegralFeignApi;
import cn.wolfcode.feign.PaymentFeignApi;
import cn.wolfcode.mapper.OrderInfoMapper;
import cn.wolfcode.mapper.PayLogMapper;
import cn.wolfcode.mapper.RefundLogMapper;
import cn.wolfcode.mq.DefaultSendCallback;
import cn.wolfcode.mq.MQConstant;
import cn.wolfcode.mq.OrderMessage;
import cn.wolfcode.redis.SeckillRedisKey;
import cn.wolfcode.service.IOrderInfoService;
import cn.wolfcode.service.ISeckillProductService;
import cn.wolfcode.util.AssertUtils;
import cn.wolfcode.util.IdGenerateUtil;
import cn.wolfcode.web.msg.SeckillCodeMsg;
import com.alibaba.fastjson.JSON;
import io.netty.util.internal.StringUtil;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/**
 * Created by wolfcode
 */
@Slf4j
@Service
@CacheConfig(cacheNames = "orders")
public class OrderInfoSeviceImpl implements IOrderInfoService {
    private final ISeckillProductService seckillProductService;
    private final OrderInfoMapper orderInfoMapper;
    private final StringRedisTemplate redisTemplate;
    private final RocketMQTemplate rocketMQTemplate;
    private final PaymentFeignApi paymentFeignApi;
    private final IntegralFeignApi integralFeignApi;
    private final PayLogMapper payLogMapper;
    private final RefundLogMapper refundLogMapper;

    public OrderInfoSeviceImpl(ISeckillProductService seckillProductService, OrderInfoMapper orderInfoMapper, StringRedisTemplate redisTemplate, PayLogMapper payLogMapper, RefundLogMapper refundLogMapper, RocketMQTemplate rocketMQTemplate, PaymentFeignApi paymentFeignApi, IntegralFeignApi integralFeignApi) {
        this.seckillProductService = seckillProductService;
        this.orderInfoMapper = orderInfoMapper;
        this.redisTemplate = redisTemplate;
        this.payLogMapper = payLogMapper;
        this.refundLogMapper = refundLogMapper;
        this.rocketMQTemplate = rocketMQTemplate;
        this.paymentFeignApi = paymentFeignApi;
        this.integralFeignApi = integralFeignApi;
    }

    @Override
    public OrderInfo selectByUserIdAndSeckillId(Long userId, Long seckillId) {
        return orderInfoMapper.selectByUserIdAndSeckillId(userId, seckillId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderInfo doSeckill(Long phone, SeckillProductVo sp) {
        // 1. 扣除库存
        seckillProductService.decrStockCount(sp.getId());
        // 2. 创建订单信息对象
        OrderInfo orderInfo = this.buildOrderInfo(phone, sp);
        // 3. 保存订单
        orderInfoMapper.insert(orderInfo);
        return orderInfo;
    }

    @Override
    public OrderInfo selectByOrderNo(String orderNo) {
        String json = redisTemplate.opsForValue().get("orders::detail:" + orderNo);
        if (StringUtils.isNotEmpty(json)) {
            OrderInfo orderInfo = JSON.parseObject(json, OrderInfo.class);
            return orderInfo;
        }
        return orderInfoMapper.selectById(orderNo);
    }

    @CachePut(key = "'detail:' + #result.orderNo")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderInfo doSeckill(Long phone, Long seckillId, Integer time) {
        SeckillProductVo sp = seckillProductService.selectByIdAndTime(seckillId, time);
        return this.doSeckill(phone, sp);
    }

    @Override
    public void failedRollback(OrderMessage message) {
        // 1. 回补 Redis 库存
        this.rollbackRedisStock(message.getSeckillId(), message.getTime());

        // 2. 删除用户下单标识
        String userOrderFlag = SeckillRedisKey.SECKILL_ORDER_HASH.join(message.getSeckillId() + "");
        redisTemplate.opsForHash().delete(userOrderFlag, message.getUserPhone() + "");

        // 3. 删除本地标识, 通过 mq 发送广播消息, 让每一个服务实例都清空自己内部的 map
        rocketMQTemplate.asyncSend(MQConstant.CANCEL_SECKILL_OVER_SIGE_TOPIC, message.getSeckillId(), new DefaultSendCallback("取消本地标识"));
    }

    private void rollbackRedisStock(Long seckillId, Integer time) {
        Long stockCount = seckillProductService.selectStockCountById(seckillId);
        String hashKey = SeckillRedisKey.SECKILL_STOCK_COUNT_HASH.join(time + "");
        redisTemplate.opsForHash().put(hashKey, seckillId + "", stockCount + "");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkPayTimeout(OrderMessage message) {
        // 1. 基于订单编号查询订单对象
        // 2. 判断状态如果不等于已支付, 就说明需要取消
        int row = orderInfoMapper.changePayStatus(message.getOrderNo(), OrderInfo.STATUS_TIMEOUT, OrderInfo.PAY_TYPE_ONLINE);
        if (row > 0) {
            // 3. MySQL 秒杀商品库存数量+1
            seckillProductService.incrStockCount(message.getSeckillId());
            // 4. 失败订单信息回滚(redis库存回补, 删除用户下单标识, 删除本地标识)
            this.failedRollback(message);
        }
    }

    @Override
    public String onlinePay(String orderNo) {
        // 1. 基于订单号, 查询订单对象
        OrderInfo orderInfo = this.selectByOrderNo(orderNo);
        // 2. 判断订单状态, 是否为未支付状态, 只有未支付状态才允许发起支付
        AssertUtils.isTrue(OrderInfo.STATUS_ARREARAGE.equals(orderInfo.getStatus()), "订单状态异常, 无法发起支付");

        // 3. 封装支付参数
        PayVo vo = new PayVo();
        vo.setBody("叩丁狼秒杀: " + orderInfo.getProductName());
        vo.setSubject(orderInfo.getProductName());
        vo.setOutTradeNo(orderNo);
        vo.setTotalAmount(orderInfo.getSeckillPrice().toString());

        // 4. 远程调用支付服务发起支付
        Result<String> result = paymentFeignApi.prepay(vo);
        return result.checkAndGet();
    }

    @Override
    public void alipaySuccess(PayResult result) {
        // 1. 获取订单信息对象
        OrderInfo orderInfo = this.selectByOrderNo(result.getOutTradeNo());
        AssertUtils.notNull(orderInfo, "订单信息有误");

        // 2. 判断订单信息是否正确
        AssertUtils.isTrue(orderInfo.getSeckillPrice().toString().equals(result.getTotalAmount()), "支付金额有误");

        // 3. 更新订单支付状态为支付成功
        int row = orderInfoMapper.changePayStatus(result.getOutTradeNo(), OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAY_TYPE_ONLINE);
        AssertUtils.isTrue(row > 0, "订单状态修改失败");

        // 4. 记录支付流水
        PayLog payLog = new PayLog();
        payLog.setPayType(PayLog.PAY_TYPE_ONLINE);
        payLog.setTotalAmount(result.getTotalAmount());
        payLog.setOutTradeNo(result.getOutTradeNo());
        payLog.setTradeNo(result.getTradeNo());
        payLog.setNotifyTime(System.currentTimeMillis() + "");
        payLogMapper.insert(payLog);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo) {
        // 1. 基于 orderNo 查询订单对象
        OrderInfo orderInfo = orderInfoMapper.selectById(orderNo);
        // 2. 判断订单状态是否为已支付
        AssertUtils.isTrue(OrderInfo.STATUS_ACCOUNT_PAID.equals(orderInfo.getStatus()), "订单状态错误");

        // 3. 判断订单支付类型, 根据不同支付类型调用不同退款接口
        Result<Boolean> result = null;
        // 封装退款 vo 对象
        RefundVo refundVo = new RefundVo(orderNo, orderInfo.getSeckillPrice().toString(), "不想要了...");
        if (orderInfo.getPayType() == OrderInfo.PAY_TYPE_ONLINE) {
            // 支付宝退款
            result = paymentFeignApi.refund(refundVo);
        } else {
            log.info("[积分退款] 准备发送积分退款事务消息: {}", JSON.toJSONString(refundVo));
            // 积分退款: 发送 RocketMQ 事务消息
            Message<RefundVo> message =
                    MessageBuilder.withPayload(refundVo)
                            .setHeader("orderNo", orderNo)
                            .build();
            TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(MQConstant.INTEGRAL_REFUND_TX_GROUP,
                    MQConstant.INTEGRAL_REFUND_TX_TOPIC, message, orderNo);
            // 判断 sendResult 是否为 COMMIT => 判断本地事务是否执行成功
            if (
                    sendResult.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE) ||
                            sendResult.getLocalTransactionState().equals(LocalTransactionState.UNKNOW)
            ) {
                log.info("[积分退款] 积分退款本地事务执行成功, 等待远程服务执行状态: {}", sendResult.getLocalTransactionState());
                return;
            }

            // 如果退款状态不为成功, 就说明失败了
            throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
        }

        // 判断远程是否退款成功, 如果成功才继续下一步
        if (result == null || result.hasError() || !result.getData()) {
            throw new BusinessException(SeckillCodeMsg.REFUND_ERROR);
        }

        // 4. 更新订单状态为已退款
        this.refundRollback(orderInfo);
    }

    private void refundRollback(OrderInfo orderInfo) {
        int row = orderInfoMapper.changeRefundStatus(orderInfo.getOrderNo(), OrderInfo.STATUS_REFUND);
        AssertUtils.isTrue(row > 0, "退款失败, 更新订单状态异常");
        // 5. 库存回补(MySQL/Redis)
        seckillProductService.incrStockCount(orderInfo.getSeckillId());
        this.rollbackRedisStock(orderInfo.getSeckillId(), orderInfo.getSeckillTime());

        // 6. 删除本地下单标识
        rocketMQTemplate.asyncSend(MQConstant.CANCEL_SECKILL_OVER_SIGE_TOPIC, orderInfo.getSeckillId(), new DefaultSendCallback("取消本地标识"));

        // 7. 创建退款日志对象保存
        RefundLog refundLog = new RefundLog();
        refundLog.setRefundReason("用户申请退款: " + orderInfo.getProductName());
        refundLog.setRefundTime(new Date());
        refundLog.setRefundType(orderInfo.getPayType());
        refundLog.setRefundAmount(orderInfo.getSeckillPrice().toString());
        refundLog.setOutTradeNo(orderInfo.getOrderNo());
        refundLogMapper.insert(refundLog);
    }

    /* 开启全局事务, 那么此时进入这个方法的代理对象就是 TM */
    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void integralPay(String orderNo, Long phone) {
        // 1. 基于订单号, 查询订单对象
        OrderInfo orderInfo = this.selectByOrderNo(orderNo);
        // 2. 判断订单状态, 是否为未支付状态, 只有未支付状态才允许发起支付
        AssertUtils.isTrue(OrderInfo.STATUS_ARREARAGE.equals(orderInfo.getStatus()), "订单状态异常, 无法发起支付");

        // 判断当前用户是否是创建这个订单的用户
        AssertUtils.isTrue(orderInfo.getUserId().equals(phone), "非法操作");

        // 3. 封装支付参数
        OperateIntergralVo vo = new OperateIntergralVo();
        vo.setInfo("积分秒杀: " + orderInfo.getProductName());
        vo.setValue(orderInfo.getIntergral());
        vo.setUserId(phone);
        vo.setOutTradeNo(orderNo);

        // 4. 远程调用支付服务发起支付
        Result<String> result = integralFeignApi.prepay(vo);
        String tradeNo = result.checkAndGet();

        // 3. 更新订单支付状态为支付成功
        int row = orderInfoMapper.changePayStatus(orderNo, OrderInfo.STATUS_ACCOUNT_PAID, OrderInfo.PAY_TYPE_INTERGRAL);
        AssertUtils.isTrue(row > 0, "订单状态修改失败");

        // 4. 记录支付流水
        PayLog payLog = new PayLog();
        payLog.setPayType(PayLog.PAY_TYPE_INTERGRAL);
        payLog.setTotalAmount(vo.getValue() + "");
        payLog.setOutTradeNo(orderNo);
        // 积分账户流水变动交易号
        payLog.setTradeNo(tradeNo);
        payLog.setNotifyTime(System.currentTimeMillis() + "");
        payLogMapper.insert(payLog);
    }

    @Override
    public void integralRefundRollback(String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.selectById(orderNo);
        this.refundRollback(orderInfo);
    }

    @Override
    public RefundLog selectRefundLogByOrderNo(String orderNo) {
        return refundLogMapper.selectByOrderNo(orderNo);
    }

    private OrderInfo buildOrderInfo(Long phone, SeckillProductVo vo) {
        Date now = new Date();
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCreateDate(now);
        orderInfo.setDeliveryAddrId(1L);
        orderInfo.setIntergral(vo.getIntergral());
        // id 生成器 => 雪花算法
        orderInfo.setOrderNo(IdGenerateUtil.get().nextId() + "");
        orderInfo.setPayType(OrderInfo.PAY_TYPE_ONLINE);
        orderInfo.setProductCount(1);
        orderInfo.setProductId(vo.getProductId());
        orderInfo.setProductImg(vo.getProductImg());
        orderInfo.setProductName(vo.getProductName());
        orderInfo.setProductPrice(vo.getProductPrice());
        orderInfo.setSeckillDate(now);
        orderInfo.setSeckillId(vo.getId());
        orderInfo.setSeckillPrice(vo.getSeckillPrice());
        orderInfo.setSeckillTime(vo.getTime());
        orderInfo.setStatus(OrderInfo.STATUS_ARREARAGE);
        orderInfo.setUserId(phone);
        return orderInfo;
    }
}
